//! Numeric helpers inspired by Lodash's number module.
//! 数值相关的 Lodash 风格工具函数集合。

use num_traits::Zero;
use rust_decimal::prelude::{Decimal, RoundingStrategy};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Add;
use std::str::FromStr;

/// Clamps `value` between `min` and `max`, mirroring `_.clamp`.
/// 类似 `_.clamp`，把数值限制在 `[min, max]` 范围内。
pub fn clamp<T>(
    value: T,
    min: T,
    max: T,
) -> T
where
    T: PartialOrd + Copy,
{
    assert!(min <= max, "min must be <= max");
    if value < min {
        min
    } else if value > max {
        max
    } else {
        value
    }
}

/// Checks whether `value` is within `[start, end)`, like `_.inRange`.
/// 类似 `_.inRange`，判断值是否落在半开区间 `[start, end)` 内。
pub fn in_range<T>(
    value: T,
    start: T,
    end: T,
) -> bool
where
    T: PartialOrd + Copy,
{
    if start <= end { value >= start && value < end } else { value >= end && value < start }
}

/// Creates a half-open arithmetic progression, similar to `_.range`.
/// 类似 `_.range`，生成一个半开区间等差序列。
pub fn range(
    start: isize,
    end: isize,
    step: Option<isize>,
) -> Vec<isize> {
    if start == end {
        return Vec::new();
    }

    let mut step = step.unwrap_or(if start < end { 1 } else { -1 });
    if step == 0 {
        return vec![start];
    }

    if (end - start).signum() != step.signum() {
        step = (end - start).signum();
    }

    let mut cursor = start;
    let mut values = Vec::new();
    if step > 0 {
        while cursor < end {
            values.push(cursor);
            cursor += step;
        }
    } else {
        while cursor > end {
            values.push(cursor);
            cursor += step;
        }
    }
    values
}

/// Aggregates values into a sum, mirroring `_.sum`.
/// 类似 `_.sum`，对迭代器中的值求和。
pub fn sum<T>(iter: impl IntoIterator<Item = T>) -> T
where
    T: Zero + Add<Output = T>,
{
    iter.into_iter().fold(T::zero(), |acc, value| acc + value)
}

/// Aggregates after applying a projection, like `_.sumBy`.
/// 类似 `_.sumBy`，先投影再求和。
pub fn sum_by<T, U, F>(
    iter: impl IntoIterator<Item = T>,
    mut projector: F,
) -> U
where
    U: Zero + Add<Output = U>,
    F: FnMut(T) -> U,
{
    iter.into_iter().fold(U::zero(), |acc, item| acc + projector(item))
}

/// Computes the arithmetic mean, returning `None` for empty iterators.
/// 计算算术平均值，若没有元素则返回 `None`。
pub fn mean(iter: impl IntoIterator<Item = f64>) -> Option<f64> {
    let mut total = 0.0;
    let mut count = 0;
    for value in iter {
        total += value;
        count += 1;
    }

    if count == 0 { None } else { Some(total / count as f64) }
}

/// Computes the median after sorting the provided values.
/// 计算中位数，会在内部对副本排序。
pub fn median(mut values: Vec<f64>) -> Option<f64> {
    if values.is_empty() {
        return None;
    }

    values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
    let mid = values.len() / 2;
    if values.len() % 2 == 0 { Some((values[mid - 1] + values[mid]) / 2.0) } else { Some(values[mid]) }
}

/// Returns the most common value, like `_.mode`.
/// 类似 `_.mode`，返回出现次数最多的元素。
pub fn mode<T>(iter: impl IntoIterator<Item = T>) -> Option<T>
where
    T: Eq + Hash + Clone,
{
    let mut counts: HashMap<T, usize> = HashMap::new();
    let mut best: Option<(T, usize)> = None;

    for value in iter {
        let counter = counts.entry(value.clone()).or_insert(0);
        *counter += 1;

        match &best {
            Some((_, best_count)) if *best_count > *counter => {},
            Some((_, best_count)) if *best_count == *counter => {},
            _ => best = Some((value, *counter)),
        }
    }

    best.map(|(value, _)| value)
}

/// Rounds a decimal to the desired scale using a half-up strategy.
/// 使用 rust_decimal 进行四舍五入，保留指定小数位（默认「四舍五入」）。
pub fn decimal_round(
    value: Decimal,
    scale: u32,
) -> Decimal {
    value.round_dp_with_strategy(scale, RoundingStrategy::MidpointAwayFromZero)
}

/// Rounds a decimal with an explicit [`RoundingStrategy`].
/// 使用自定义的 [`RoundingStrategy`] 进行进位控制。
pub fn decimal_round_with_strategy(
    value: Decimal,
    scale: u32,
    strategy: RoundingStrategy,
) -> Decimal {
    value.round_dp_with_strategy(scale, strategy)
}

/// Floors a decimal at the given scale.
/// 按指定精度向下取整。
pub fn decimal_floor(
    value: Decimal,
    scale: u32,
) -> Decimal {
    value.round_dp_with_strategy(scale, RoundingStrategy::ToNegativeInfinity)
}

/// Ceils a decimal at the given scale.
/// 按指定精度向上取整。
pub fn decimal_ceil(
    value: Decimal,
    scale: u32,
) -> Decimal {
    value.round_dp_with_strategy(scale, RoundingStrategy::ToPositiveInfinity)
}

/// Truncates extra decimal places without rounding.
/// 截断多余小数位，不做进位。
pub fn decimal_truncate(
    value: Decimal,
    scale: u32,
) -> Decimal {
    value.round_dp_with_strategy(scale, RoundingStrategy::ToZero)
}

/// Sums decimal values precisely using [`Decimal`] arithmetic.
/// 使用 [`Decimal`] 进行精确求和。
pub fn decimal_sum(iter: impl IntoIterator<Item = Decimal>) -> Decimal {
    iter.into_iter().fold(Decimal::ZERO, |acc, value| acc + value)
}

/// Parses an integer similar to JavaScript's `parseInt`.
/// 仿照 JS `parseInt`，支持可选进制与常见前缀（0x/0o/0b），出现非法字符即停止解析。
pub fn parse_int(
    input: &str,
    radix: Option<u32>,
) -> Option<i64> {
    let trimmed = input.trim_start();
    if trimmed.is_empty() {
        return None;
    }

    let mut rest = trimmed;
    let mut sign: i128 = 1;
    if let Some(first) = rest.chars().next() {
        if first == '-' || first == '+' {
            if first == '-' {
                sign = -1;
            }
            rest = &rest[first.len_utf8()..];
        }
    }

    if rest.is_empty() {
        return None;
    }

    let mut base = radix.unwrap_or(10);
    if radix.is_none() {
        if rest.starts_with("0x") || rest.starts_with("0X") {
            base = 16;
            rest = &rest[2..];
        } else if rest.starts_with("0o") || rest.starts_with("0O") {
            base = 8;
            rest = &rest[2..];
        } else if rest.starts_with("0b") || rest.starts_with("0B") {
            base = 2;
            rest = &rest[2..];
        }
    }

    if !(2..=36).contains(&base) || rest.is_empty() {
        return None;
    }

    let mut value: i128 = 0;
    let mut consumed = false;
    for ch in rest.chars() {
        if let Some(d) = ch.to_digit(base) {
            consumed = true;
            value = value.checked_mul(base as i128)?.checked_add(d as i128)?;
        } else {
            break;
        }
    }

    if !consumed {
        return None;
    }

    value = value.checked_mul(sign)?;
    if value < i64::MIN as i128 || value > i64::MAX as i128 { None } else { Some(value as i64) }
}

/// Parses a floating-point number similar to JavaScript's `parseFloat`.
/// 仿照 JS `parseFloat`，读取到首个非法字符为止（支持指数、以点开头的写法等）。
pub fn parse_float(input: &str) -> Option<f64> {
    let segment = extract_float_segment(input)?;
    segment.parse::<f64>().ok()
}

/// Parses a decimal using `rust_decimal`, mirroring `parseFloat` semantics.
/// 基于 `rust_decimal` 的高精度解析，遵循 `parseFloat` 的截断逻辑。
pub fn parse_decimal(input: &str) -> Option<Decimal> {
    let segment = extract_float_segment(input)?;
    Decimal::from_str(segment).ok()
}

fn extract_float_segment(input: &str) -> Option<&str> {
    let trimmed = input.trim_start();
    if trimmed.is_empty() {
        return None;
    }

    let bytes = trimmed.as_bytes();
    let len = bytes.len();
    let mut i = 0;

    if i < len && (bytes[i] == b'+' || bytes[i] == b'-') {
        i += 1;
    }

    let mut last_valid = i;
    let mut seen_digit = false;
    let mut seen_dot = false;
    let mut seen_exp = false;
    let mut exp_sign_allowed = false;
    let mut exp_digit = false;

    while i < len {
        match bytes[i] {
            b'0'..=b'9' => {
                seen_digit = true;
                if seen_exp {
                    exp_digit = true;
                }
                exp_sign_allowed = false;
                i += 1;
                last_valid = i;
            },
            b'.' if !seen_dot && !seen_exp => {
                seen_dot = true;
                i += 1;
                last_valid = i;
            },
            b'e' | b'E' if seen_digit && !seen_exp => {
                seen_exp = true;
                exp_sign_allowed = true;
                exp_digit = false;
                i += 1;
                last_valid = i;
            },
            b'+' | b'-' if exp_sign_allowed => {
                exp_sign_allowed = false;
                i += 1;
                last_valid = i;
            },
            _ => break,
        }
    }

    if !seen_digit || (seen_exp && !exp_digit) || last_valid == 0 { None } else { Some(&trimmed[..last_valid]) }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn clamp_and_range_behave_like_lodash() {
        assert_eq!(clamp(10, 0, 5), 5);
        assert_eq!(clamp(-1, 0, 5), 0);
        assert_eq!(range(0, 4, None), vec![0, 1, 2, 3]);
        assert_eq!(range(3, 0, None), vec![3, 2, 1]);
        assert_eq!(range(0, 0, None), Vec::<isize>::new());
    }

    #[test]
    fn arithmetic_helpers_cover_sum_variants() {
        assert_eq!(sum([1, 2, 3]), 6);
        assert_eq!(sum_by(["foo", "bar"], |s| s.len()), 6usize);
        assert_eq!(mean([2.0, 4.0, 6.0]).unwrap(), 4.0);
        assert_eq!(median(vec![3.0, 1.0, 2.0]).unwrap(), 2.0);
        assert_eq!(median(vec![1.0, 2.0, 3.0, 4.0]).unwrap(), 2.5);
    }

    #[test]
    fn mode_and_range_cover_edge_cases() {
        assert!(mode::<i32>([]).is_none());
        assert_eq!(mode([1, 1, 2, 3]).unwrap(), 1);
        assert_eq!(mode(["a", "b", "b", "a", "b"]).unwrap(), "b");
        assert!(in_range(3, 2, 5));
        assert!(in_range(3, 5, 2));
        assert!(!in_range(5, 0, 4));
    }

    #[test]
    fn decimal_helpers_cover_rounding_strategies() {
        let value = Decimal::from_str("123.4567").unwrap();
        assert_eq!(decimal_round(value, 2), Decimal::from_str("123.46").unwrap());
        assert_eq!(decimal_round_with_strategy(value, 2, RoundingStrategy::MidpointTowardZero), Decimal::from_str("123.46").unwrap());
        assert_eq!(decimal_floor(value, 2), Decimal::from_str("123.45").unwrap());
        assert_eq!(decimal_ceil(value, 2), Decimal::from_str("123.46").unwrap());
        assert_eq!(decimal_truncate(value, 2), Decimal::from_str("123.45").unwrap());

        let sum = decimal_sum([Decimal::from_str("1.5").unwrap(), Decimal::from_str("2.25").unwrap(), Decimal::from_str("0.25").unwrap()]);
        assert_eq!(sum, Decimal::from_str("4.0").unwrap());
    }

    #[test]
    fn parse_helpers_follow_js_semantics() {
        assert_eq!(parse_int("42", None), Some(42));
        assert_eq!(parse_int("   -0x1f", None), Some(-31));
        assert_eq!(parse_int("1010", Some(2)), Some(10));
        assert_eq!(parse_int("zz", Some(36)), Some(1295));
        assert_eq!(parse_int("abc", Some(10)), None);

        assert_eq!(parse_float("  3.14px"), Some(3.14));
        assert_eq!(parse_float("-.5"), Some(-0.5));
        assert_eq!(parse_float("1e2ms"), Some(100.0));
        assert_eq!(parse_float("e3"), None);

        assert_eq!(parse_decimal(" 12.345more").unwrap(), Decimal::from_str("12.345").unwrap());
        assert!(parse_decimal("text").is_none());
    }
}
